#include "HistogramCompression.h"


#include "TimingEngine.hh"
#include "liberty/Liberty.hh"
#include <ctime>
#include "usage/usage.hh"
// #include "QuadraticTDP.hh"

namespace ieda_contest {
// #define LOCAL_WIRE_CAP_PER_MICRON 0.16e-15
// #define LOCAL_WIRE_RES_PER_MICRON 2.535
// #define DEBUG

// HistogramCompression::HistogramCompression(ista::TimingEngine* timing_engine, ContestDB* database);
// {

// }


/**
 * @brief Perform Slack Histogram Compression
 * 
 */
void HistogramCompression::runHistogramCompressionOpt1()
{
  LOG_INFO << "-----------------------------HistogramCompressionOpt is started ! ! !-------------------------------";
  _timing_engine->updateTiming();
  ieda::Stats hc_status;
  // _timing_engine->reportTiming();

  while (maxIter) {
    netWeightUpdate();      // 更新线网权重
    discreteLocalSearch();  // 对单元做局部离散搜索
    maxIter--;
  }
  _timing_engine->updateTiming();
  double time_delta = hc_status.elapsedRunTime();
  LOG_INFO << "HistogramCompressionOpt Time Elapsed: " << time_delta << "s";
  // _timing_engine->reportTiming();
  // std::cout << "The run time is:" <<(double)(endTime - startTime) / CLOCKS_PER_SEC << "s" << std::endl;
  LOG_INFO << "-----------------------------HistogramCompressionOpt is finished ! ! !-------------------------------";
}

// init
void HistogramCompression::buildTraverseOrder()
{
  std::list<ContestPin*> pinsQueue;
  // initial
  for (auto cell : _Contest_database->get_ContestInstance_pvec()) {
    cell->set_marked(false);
  }

  // traverse

  for (auto pin : _Contest_database->get_ContestPin_pvec()) {
    if (pin->isPI()) {
      pinsQueue.push_back(pin);
    }
  }
  while (!pinsQueue.empty()) {
    auto thePin = pinsQueue.front();
    std::string pin_name = thePin->get_name();
    std::string port_name = pin_name.substr(pin_name.length() - 1, pin_name.length());
    pinsQueue.pop_front();
    // cout << "port: " << thePin->name << " is visited." << endl;
    if (thePin->isPI() || thePin->isInstanceOutputPin() || thePin->isClockPin()) {  // primary Input or output port
      auto net = thePin->get_ContestNet();
      for (auto sink : net->get_sink_pins()) {
        pinsQueue.push_back(sink);
      }
    } else if (thePin->isInstanceInputPin() || thePin->isFlipFlopClkPin() || port_name == "D") {  // input port
      // add the cell into list & push output pins
      auto theCell = thePin->get_ContestInstance();
      if (theCell->get_marked())
        continue;
      // visit the cell
      traverseList.push_back(theCell);
      theCell->set_marked(true);
      // push output pins
      for (auto outpin : theCell->get_outpin_vec()) {
        pinsQueue.push_back(outpin);
      }
    }
  }
}

void HistogramCompression::initWeight()
{
  // 相当于将cell的inpin的weight都设置为1了。
  for (auto net : _Contest_database->get_ContestNet_pvec()) {
    // double sourceEarlySlk=pins[nets[i].source].earlySlk;
    // double sourceLateSlk=pins[nets[i].source].lateSlk;
    for (auto sink : net->get_sink_pins()) {
      net->_late_weight[sink] = 1;
      net->_early_weight[sink] = 1;
    }
  }
  // below needs to be reverse topological order!!!!!
  for (std::vector<ContestInstance*>::iterator it = traverseList.begin(); it != traverseList.end(); it++)
    netWeightDistribute(*it);  // 将netweight 分配到cell 的pin上
}

void HistogramCompression::netWeightDistribute(ContestInstance* inst)
{
  /// \brief 通过后一个cell的oupin weight分配到前一个cell的inpin weight上！

  double inEarlyWeight = 0.0;
  double outEarlyWeight = 0.0;
  double inLateWeight = 0.0;   // 该单元的所有inpin weight之和
  double outLateWeight = 0.0;  // 该单元的所有outpin weight之和

  for (auto cellPin : inst->get_pin_vec()) {
    // 注意：不同的例子取 “D”的位置不同！！！！
    std::string pin_name = cellPin->get_name();
    std::string port_name = pin_name.substr(pin_name.length() - 1, pin_name.length());
    auto net = cellPin->get_ContestNet();

    for (auto netSink : net->get_sink_pins()) {
      if (cellPin->isInstanceInputPin() || port_name == "D") {
        if (netSink == cellPin) {
          inLateWeight += net->_late_weight[netSink];
          inEarlyWeight += net->_early_weight[netSink];
        }
      } else if (cellPin->isInstanceOutputPin()) {
        outLateWeight += net->_late_weight[netSink];
        outEarlyWeight += net->_early_weight[netSink];
      }
    }  
  }
#ifdef DEBUG
  std::cout << "inst " << inst->get_name() << " have early in total: " << inEarlyWeight << " and late in total " << inLateWeight
            << std::endl;
  std::cout << "inst " << inst->get_name() << " have early out total: " << outEarlyWeight << " and late out total " << outLateWeight
            << std::endl;
#endif

  // 下面操作是不处理cell outpin，而是将cell outpin上的权重累乘到cell inpin上！
  // 具体操作为： inpin weight = inpin weight * （outpinweight/inpinweight）
  for (auto cellPin : inst->get_pin_vec()) {
    std::string pin_name = cellPin->get_name();
    std::string port_name = pin_name.substr(pin_name.length() - 1, pin_name.length());
    auto net = cellPin->get_ContestNet();  //
    for (auto netSink : net->get_sink_pins()) {
      if (netSink == cellPin) {
        if (cellPin->isInstanceInputPin() || port_name == "D") {
#ifdef DEBUG
          double gg = cellPin->get_ContestNet()->_early_weight[cellPin];
          double ggg = cellPin->get_ContestNet()->_late_weight[cellPin];
          std::string ii = cellPin->get_ContestNet()->get_name();
          std::cout << "changing net " << ii << " earlyWeight " << gg << " to " << (gg * (outEarlyWeight / inEarlyWeight)) << std::endl;
          std::cout << "changing net " << ii << " lateWeight " << ggg << " to " << (ggg * (outLateWeight / inLateWeight)) << std::endl;
#endif
          net->_late_weight[netSink] *= (outLateWeight / inLateWeight);
          net->_early_weight[netSink] *= (outEarlyWeight / inEarlyWeight);
        }
      }
    }
  }
}

void HistogramCompression::initHPWL()
{
  ///\brief 得到每个cell outpin到后面cell的HPWL线长！

  for (auto cell : _Contest_database->get_ContestInstance_pvec()) {
    // HPWL for output interconnect
    double netMaxX, netMinX;
    double netMaxY, netMinY;
    for (auto pin : cell->get_pin_vec()) {
      if (pin->isInstanceOutputPin())  // may have two outputs
      {
        auto theNet = pin->get_ContestNet();  // one net can have two fan outs
        netMaxX = netMinX = pin->get_center_coord().get_x();
        netMaxY = netMinY = pin->get_center_coord().get_y();
        
        for (auto net_sink : theNet->get_sink_pins()) {
          netMaxX = std::max(netMaxX, static_cast<double>(net_sink->get_center_coord().get_x()));
          netMinX = std::min(netMinX, static_cast<double>(net_sink->get_center_coord().get_x()));
          netMaxY = std::max(netMaxY, static_cast<double>(net_sink->get_center_coord().get_y()));
          netMinY = std::min(netMinY, static_cast<double>(net_sink->get_center_coord().get_y()));
          // cout<<"Changing (netMinX, netMaxX, netMinY, netMaxY) to "<<netMinX<<" , "<<netMaxX<<" , "<<netMinY<<" , "<<netMaxY<<endl;
        }
      }
    }
    cell->set_hpwl((netMaxX - netMinX + netMaxY - netMinY) / static_cast<double>(_Contest_database->get_ContestLayout()->get_database_unit()));
  }
}

// utility

void HistogramCompression::netWeightUpdate()
{
  // update PO（timing_endpoints)
  for (auto pin : _Contest_database->get_ContestPin_pvec()) {
    std::string pin_name = pin->get_name();
    std::string port_name = pin_name.substr(pin_name.length() - 1, pin_name.length());
    if (pin->isPO() || port_name == "D") {
      pin->set_lateWeight(pin->get_lateWeight() * (pin->get_arrival_late_time()) / (pin->get_required_late_time()));
      pin->set_earlyWeight(pin->get_earlyWeight() * (pin->get_required_early_time()) / (pin->get_arrival_early_time()));
    }
  }

  // below needs to be reverse topological order!!!!!
  for (std::vector<ContestInstance*>::iterator it = traverseList.begin(); it != traverseList.end(); it++)
    netWeightBalance(*it);
  /////////////
  for (std::vector<ContestInstance*>::iterator it = traverseList.begin(); it != traverseList.end(); it++)
    netWeightDistribute(*it);
  /////////////////
}

void HistogramCompression::netWeightBalance(ContestInstance* cell)
{
  ///\brief 用于将cell inpin weight 更新到前一个 cell inpin weight 上，分为逻辑单元和DFF。

  if (cell->isBuffer())  // ignoring LCBs
  {
#ifdef DEBUG
    // cout<<"LLLLCCCCBBB"<<endl;
#endif
  } else {
    for (auto pin : cell->get_pin_vec()) {
      std::string pin_name = pin->get_name();
      std::string port_name = pin_name.substr(pin_name.length() - 1, pin_name.length());
      auto net = pin->get_ContestNet();
      if (pin->isInstanceInputPin()) {
        for (auto sink : net->get_sink_pins()) {
          if (sink == pin) {
            ContestPin* outpin = nullptr;
            for (auto cellpin : cell->get_outpin_vec()) {
              outpin = cellpin;
            }
            net->_late_weight[pin] *= ((pin->get_arrival_late_time()) / outpin->get_arrival_late_time());
            net->_early_weight[pin] *= (outpin->get_arrival_early_time() / (pin->get_arrival_early_time()));
            // net->_late_weight[pin] *= std::pow(((pin->get_arrival_late_time()) / outpin->get_arrival_late_time()),4);
            // net->_late_weight[pin] *= std::pow(((pin->get_arrival_early_time()) / outpin->get_arrival_early_time()),4);


          }
        }
      } else if (port_name == "D")  // DFF
      {
        for (auto sink : net->get_sink_pins()) {
          if (sink == pin) {
            ContestPin* outpin = nullptr;
            for (auto cellpin : cell->get_outpin_vec()) {
              outpin = cellpin;
            }
            net->_late_weight[pin] *= ((pin->get_arrival_late_time()) / outpin->get_arrival_late_time());
            net->_early_weight[pin] *= (outpin->get_arrival_early_time() / (pin->get_arrival_early_time()));
            // net->_late_weight[pin] *= std::pow(((pin->get_arrival_late_time()) / outpin->get_arrival_late_time()),4);
            // net->_late_weight[pin] *= std::pow(((pin->get_arrival_early_time()) / outpin->get_arrival_early_time()),4);
          }
        }
      }
    }
  }
}

void HistogramCompression::discreteLocalSearch()
{
  // below needs to be reverse topological order!!!!!
  for (std::vector<ContestInstance*>::iterator it = traverseList.begin(); it != traverseList.end(); it++) {
    ContestInstance* cell = *it;
    if (!cell->isFixed())
      localSearch(cell);
  }
}

void HistogramCompression::localSearch(ContestInstance* cell)
{

  // 处理单个cell的情况
  double best_cost = std::numeric_limits<double>::max();  // std::numeric_limits<double>::max();
  double best_sit = static_cast<double>(cell->get_center_coord().get_x());
  double best_y =  static_cast<double>(cell->get_center_coord().get_y());
  // int total_windows=8;//actually only seven pos is searched
  int A1 = 1;
  // int A2=1;
  // int beta=1;
  /// below are weight for late and early cost
  // double alpha=5.0;
      int distance = 1;
      int unit = dmInst->get_idb_builder()->get_def_service()->get_design()->get_units()->get_micron_dbu();
      std::optional<double> width = std::nullopt;
      double LOCAL_WIRE_CAP_PER_MICRON = dynamic_cast<ista::TimingIDBAdapter*>(_Contest_database->get_timing_engine()->get_db_adapter())->getCapacitance(3, distance / 1.0 / unit, width);
      double LOCAL_WIRE_RES_PER_MICRON = dynamic_cast<ista::TimingIDBAdapter*>(_Contest_database->get_timing_engine()->get_db_adapter())->getResistance(3, distance / 1.0 / unit, width);

  // int32_t original_y = cell->get_center_coord().get_y();

  // for待办！！！
  for (unsigned int i = 1; i < total_windows_x; ++i) {
    for(unsigned int j = 0; j <= total_windows_y; ++j){
    /// may have prob!? discrete
    // int32_t numSites = _Contest_database->get_ContestLayout()->get_row_2d_list()[0][0]->get_site_num();
    int32_t size_width = _Contest_database->get_ContestLayout()->get_site_width();
    int32_t row_hight = _Contest_database->get_ContestLayout()->get_row_height();
    // 1.获取新位置
    int32_t newcoord = cell->get_center_coord().get_x() + i * size_width;  // row的step
    int32_t newcoord_y = cell->get_center_coord().get_y() + j * row_hight;

    // if(j >= _Contest_database->get_ContestLayout()->get_row_2d_list().size()) continue;

    // if (newcoord + cell->get_width()
    //     > _Contest_database->get_ContestLayout()->get_row_2d_list()[0][0]->get_coordinate().get_x() + numSites * size_width)
    //   break;

    // 2.移动单元位置
    // cell->set_center_point(Point<int32_t>(newcoord, original_y));
    cell->set_center_point(Point<int32_t>(newcoord, newcoord_y));

    // 3.计算cost
    double cost_Late = 0.0;
    double cost_Early = 0.0;
    double netMaxX = 0;
    double netMinX = 0;
    double netMaxY = 0;
    double netMinY = 0;
    for (auto pin : cell->get_pin_vec()) {
      if (pin->isInstanceOutputPin()) {
        auto theNet = pin->get_ContestNet();  // one net can have two fan outs
        netMaxX = netMinX = pin->get_center_coord().get_x();
        netMaxY = netMinY = pin->get_center_coord().get_y();
        for (auto sink : theNet->get_sink_pins()) {
          netMaxX = std::max(netMaxX, static_cast<double>(sink->get_center_coord().get_x()));
          netMinX = std::min(netMinX, static_cast<double>(sink->get_center_coord().get_x()));
          netMaxY = std::max(netMaxY, static_cast<double>(sink->get_center_coord().get_y()));
          netMinY = std::min(netMinY, static_cast<double>(sink->get_center_coord().get_y()));
        }
      }
    }
    // cout<<"where do you crash? "<<endl;
    double new_HPWL = (netMaxX - netMinX + netMaxY - netMinY) / this->_Contest_database->get_ContestLayout()->get_database_unit();

    // double delta_CAP = (new_HPWL - cell->get_hpwl()) * LOCAL_WIRE_CAP_PER_MICRON * 10000000;

    double delta_CAP = (new_HPWL - cell->get_hpwl()) * LOCAL_WIRE_CAP_PER_MICRON;

    // std::cout<<"The original HPWL is "<< cell->get_hpwl()<<" new_HPWL is "<<new_HPWL<<std::endl;

    double delay_Early = 0;
    double delay_Late = 0;
    for (auto sinknow : cell->get_inpin_vec()) {
      // for fan in
      // cell inpins
      for (auto pin : sinknow->get_ContestNet()->get_sink_pins()) {
        if (!pin) continue;
        if (pin == sinknow) {
          // I think the two below is the same!!
          // wire delay cost.
          auto driver = sinknow->get_ContestNet()->get_driver_pin();
          if(!driver) {
              continue;
          }else{
           delay_Early = sinknow->get_arrival_early_time() - driver->get_arrival_early_time();
           delay_Late = sinknow->get_arrival_late_time() - driver->get_arrival_late_time();
          }

          // cost = input_wire_delay_cost + cell_delay_cost
          cost_Late += (delay_Late + A1 * delta_CAP) * sinknow->get_ContestNet()->_late_weight[sinknow];
          cost_Early += (delay_Early + A1 * delta_CAP) * sinknow->get_ContestNet()->_early_weight[sinknow];
#ifdef DEBUG
          // cout<<"delay_Late is "<<delay_Late<<" , A1*delta_CAP is "<<A1*delta_CAP<<endl;
          // cout<<"delay_Early is "<<delay_Early<<endl;
#endif
        }
      }
    }
    for (auto outpin : cell->get_outpin_vec())
    // for fan out
    {
      if(!outpin) continue;
      for (auto pin : outpin->get_ContestNet()->get_sink_pins()) {
        double MANHAT = 0.0;
        if (!pin->isPO())  // not PO
        {
          MANHAT = (abs(cell->get_center_coord().get_x() - pin->get_ContestInstance()->get_center_coord().get_x())
                    + abs(cell->get_center_coord().get_y() - pin->get_ContestInstance()->get_center_coord().get_y()))
                   / static_cast<double>(_Contest_database->get_ContestLayout()->get_database_unit());
        } else {
          MANHAT = (abs(cell->get_center_coord().get_x() - pin->get_center_coord().get_x())
                    + abs(cell->get_center_coord().get_y() - pin->get_center_coord().get_y()))
                   / static_cast<double>(_Contest_database->get_ContestLayout()->get_database_unit());
        }

        double ref_wire_delay = pin->get_arrival_late_time() - outpin->get_arrival_late_time();
        double pin_cap = getPinCap(pin);

        // 线负载电容计算方法！
        //  double  wire_cap = dynamic_cast<TimingIDBAdapter *>(_db_adapter)
        //                ->getCapacitance(1, (double)wire_length / dbu, width);

        double delta_TOW
            = 0.69 * LOCAL_WIRE_RES_PER_MICRON * MANHAT * (LOCAL_WIRE_CAP_PER_MICRON * new_HPWL / 2 + pin_cap) - ref_wire_delay;
        cost_Late += (ref_wire_delay + delta_TOW) * outpin->get_ContestNet()->_late_weight[pin];
        cost_Early += (ref_wire_delay + delta_TOW) * outpin->get_ContestNet()->_early_weight[pin];
      }
    }

    double cost_now = alpha * cost_Late + omega * cost_Early;
    if (onlyConsiderLate) {
      cost_now = cost_Late;
    }
#ifdef DEBUG
    // cout<<"LOCAL searching... cell "<<cells[cellid].name<<" windows "<<i<<"current location is "<<cells[cellid].x_coord<<endl;
    // cout<<"The cost is "<<cost_now<<endl<<endl<<endl;
#endif
    if (cost_now < best_cost) {
      best_cost = cost_now;
      best_sit = cell->get_center_coord().get_x();
      best_y = cell->get_center_coord().get_y();
    }
    }
  }
  // cell->set_center_point(Point<int32_t>((double) best_sit, original_y));
  
  cell->set_center_point(Point<int32_t>((double) best_sit, best_y));

}



double HistogramCompression::getPinCap(ContestPin* pin)
{
  auto driver_name = pin->get_name();
  auto* sta_vertex = _timing_engine->findVertex(driver_name.c_str());
  if (sta_vertex == NULL) return 0;
  auto* pin_obj = sta_vertex->get_design_obj();
  if(pin_obj == nullptr ) return 0;
  ista::Pin* pin_ptr = dynamic_cast<ista::Pin*>(pin_obj);
  if(!pin_ptr) 
  return 0;
  
  ista::LibertyPort* lib_port = pin_ptr->get_cell_port();
  double pin_cap = lib_port->get_port_cap();
  return pin_cap;
}

};  // namespace ipl